{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Benchmark Throughput experiment (point to point)\n",
    "This notebook will show you how to measure the throughput between two Alveo nodes using the benchmark application with UDP as a transport protocol.\n",
    "We are going to rely on a Dask cluster to configure the local and remote Alveo cards.\n",
    "\n",
    "This notebook assumes:\n",
    "* Direct connection between the Alveo cards\n",
    "* Dask cluster is already created and running. For more information about setting up a Dask cluster visit the [Dask documentation](https://docs.dask.org/en/latest/setup.html)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Connect to the Dask cluster\n",
    "1. Connect to the Dask cluster\n",
    "1. Grab workers name\n",
    "1. Check if there are two workers available"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table style=\"border: 2px solid white;\">\n",
       "<tr>\n",
       "<td style=\"vertical-align: top; border: 0px solid white\">\n",
       "<h3 style=\"text-align: left;\">Client</h3>\n",
       "<ul style=\"text-align: left; list-style: none; margin: 0; padding: 0;\">\n",
       "  <li><b>Scheduler: </b>tcp://10.1.212.126:8786</li>\n",
       "  <li><b>Dashboard: </b><a href='http://10.1.212.126:8787/status' target='_blank'>http://10.1.212.126:8787/status</a>\n",
       "</ul>\n",
       "</td>\n",
       "<td style=\"vertical-align: top; border: 0px solid white\">\n",
       "<h3 style=\"text-align: left;\">Cluster</h3>\n",
       "<ul style=\"text-align: left; list-style:none; margin: 0; padding: 0;\">\n",
       "  <li><b>Workers: </b>2</li>\n",
       "  <li><b>Cores: </b>32</li>\n",
       "  <li><b>Memory: </b>232.35 GB</li>\n",
       "</ul>\n",
       "</td>\n",
       "</tr>\n",
       "</table>"
      ],
      "text/plain": [
       "<Client: 'tcp://10.1.212.126:8786' processes=2 threads=32, memory=232.35 GB>"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from dask.distributed import Client, get_client\n",
    "\n",
    "client = Client(\"tcp://10.1.212.126:8786\")\n",
    "client"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "client_info = client.scheduler_info()['workers']\n",
    "workers = []\n",
    "for cli in client_info:\n",
    "    workers.append(client_info[cli]['name'])\n",
    "\n",
    "if len(workers) != 2:\n",
    "    print(\"Configure your Dask cluster with two workers\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Basic remote functions\n",
    "In this part we are going to schedule a basic function to the workers to verify that we are able to pinpoint tasks to a particular worker, we are also going to grab the Alveo shell name.\n",
    "You should visually check that your xclbin file is built for the Alveo shell available on the workers."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Worker name: alveo3b | shell version: \"xilinx_u280_xdma_201920_3\"\n",
      "Worker name: alveo3c | shell version: \"xilinx_u280_xdma_201920_3\"\n"
     ]
    }
   ],
   "source": [
    "import platform, os\n",
    "\n",
    "def verify_workers():\n",
    "    node_name = platform.node()\n",
    "    shell_version = os.popen(\"xbutil dump | grep dsa_name\").read()\n",
    "    #match = True\n",
    "    #if 'xilinx_u280_xdma_201920_3' not in shell_version:\n",
    "    #    match = False\n",
    "    return node_name, shell_version[24:-2]\n",
    "\n",
    "worker_0 = client.submit(verify_workers ,workers=workers[0], pure=False)\n",
    "worker_1 = client.submit(verify_workers ,workers=workers[1], pure=False)\n",
    "\n",
    "worker_check = [worker_0.result(),worker_1.result()]\n",
    "\n",
    "for w in worker_check:\n",
    "    print('Worker name: {} | shell version: {}'.format(w[0],w[1]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Source Dask device and utilities\n",
    "\n",
    "In this section we will import the libraries and dask on pynq class which allow us to:\n",
    "\n",
    "* Download a `xclbin` file to a worker\n",
    "* Peek and poke registers\n",
    "* Allocate buffers\n",
    "* Start kernels\n",
    "\n",
    "All of these capabilities are available for both local and remote workers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/javascript": [
       "\n",
       "try {\n",
       "require(['notebook/js/codecell'], function(codecell) {\n",
       "  codecell.CodeCell.options_default.highlight_modes[\n",
       "      'magic_text/x-csrc'] = {'reg':[/^%%microblaze/]};\n",
       "  Jupyter.notebook.events.one('kernel_ready.Kernel', function(){\n",
       "      Jupyter.notebook.get_cells().map(function(cell){\n",
       "          if (cell.cell_type == 'code'){ cell.auto_highlight(); } }) ;\n",
       "  });\n",
       "});\n",
       "} catch (e) {};\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "from vnx_utils import *\n",
    "import pynq\n",
    "%run dask_pynq.py"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Download xclbin to workers\n",
    "1. Create Dask device for each worker\n",
    "2. Create an overlay object for each worker, this step will download the `xclbin` file to the Alveo card"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "/mnt/scratch/marruiz/conda/lib/python3.7/site-packages/distributed/worker.py:3321: UserWarning: Large object of size 50.18 MB detected in task graph: \n",
      "  (b'xclbin2\\x00\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff ... ROR_DATA_END',)\n",
      "Consider scattering large objects ahead of time\n",
      "with client.scatter to reduce scheduler burden and \n",
      "keep data on workers\n",
      "\n",
      "    future = client.submit(func, big_data)    # bad\n",
      "\n",
      "    big_future = client.scatter(big_data)     # good\n",
      "    future = client.submit(func, big_future)  # good\n",
      "  % (format_bytes(len(b)), s)\n"
     ]
    }
   ],
   "source": [
    "daskdev_w0 = DaskDevice(client, workers[0])\n",
    "daskdev_w1 = DaskDevice(client, workers[1])\n",
    "\n",
    "xclbin = '../benchmark.intf3.xilinx_u280_xdma_201920_3/vnx_benchmark_if3.xclbin'\n",
    "ol_w0 = pynq.Overlay(xclbin, device=daskdev_w0)\n",
    "ol_w1 = pynq.Overlay(xclbin, device=daskdev_w1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Check Link \n",
    "\n",
    "We are going to use the function `link_status` that reports if the CMAC is detecting link, which means that the physical connection\n",
    "between the two Alveo cards is established."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Link worker 0 {'cmac_link': True}; link worker 1 {'cmac_link': True}"
     ]
    }
   ],
   "source": [
    "print(\"Link worker 0 {}; link worker 1 {}\".format(ol_w0.cmac_1.link_status(),ol_w1.cmac_1.link_status()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Configure remote Alveo card\n",
    "\n",
    "1. Set up IP address and MAC address\n",
    "2. Set up connection table\n",
    "3. Launch ARP discovery\n",
    "4. Print out ARP Table "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'HWaddr': '00:0a:35:02:9d:0a', 'inet addr': '192.168.0.10', 'gateway addr': '192.168.0.1', 'Mask': '255.255.255.0'}\n",
      "Position   5\tMAC address 00:0a:35:02:9d:e5\tIP address 192.168.0.5\n"
     ]
    }
   ],
   "source": [
    "print(ol_w1.networklayer_1.set_ip_address('192.168.0.10', debug=True))\n",
    "#2\n",
    "ol_w1.networklayer_1.sockets[1] = ('192.168.0.5', 62177, 60512, True)\n",
    "ol_w1.networklayer_1.populate_socket_table()\n",
    "#3 \n",
    "ol_w1.networklayer_1.arp_discovery()\n",
    "#4\n",
    "ol_w1.networklayer_1.get_arp_table()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Configure local Alveo card\n",
    "\n",
    "1. Print out IP and MAC address\n",
    "2. Set up connection table\n",
    "3. Launch ARP discovery\n",
    "4. Print out ARP Table "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "{'HWaddr': '00:0a:35:02:9d:e5', 'inet addr': '192.168.0.5', 'gateway addr': '192.168.0.1', 'Mask': '255.255.255.0'}\n",
      "Position  10\tMAC address 00:0a:35:02:9d:ea\tIP address 192.168.0.10\n"
     ]
    }
   ],
   "source": [
    "print(ol_w0.networklayer_1.get_network_info())\n",
    "#2\n",
    "ol_w0.networklayer_1.sockets[7] = ('192.168.0.10', 60512, 62177, True)\n",
    "ol_w0.networklayer_1.populate_socket_table()\n",
    "#3 \n",
    "ol_w0.networklayer_1.arp_discovery()\n",
    "#4\n",
    "ol_w0.networklayer_1.get_arp_table()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Configure application\n",
    "* Configure remote traffic generator 1 in `CONSUMER` mode"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "ol_w1_tg = ol_w1.traffic_generator_1_1\n",
    "ol_w1_tg.register_map.debug_reset = 1\n",
    "ol_w1_tg.start(TgMode.CONSUMER)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "* Configure local traffic generator 1\n",
    "* Run the application for different packet sizes\n",
    "* Compute and store results for both local (Tx) and remote (Rx)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Sent      1,000,000 size:   64-Byte done!\tGot      1,000,000 took   0.0104 sec, thr: 49.231 Gbps, theoretical: 49.231 Gbps, difference: -0.0005 Gbps\n",
      "Sent      1,000,000 size:  128-Byte done!\tGot      1,000,000 took   0.0167 sec, thr: 61.297 Gbps, theoretical: 65.979 Gbps, difference: 4.6825 Gbps\n",
      "Sent      1,000,000 size:  192-Byte done!\tGot      1,000,000 took   0.0206 sec, thr: 74.419 Gbps, theoretical: 74.419 Gbps, difference: -0.0008 Gbps\n",
      "Sent      1,000,000 size:  256-Byte done!\tGot      1,000,000 took   0.0258 sec, thr: 79.504 Gbps, theoretical: 79.503 Gbps, difference: -0.0008 Gbps\n",
      "Sent      1,000,000 size:  320-Byte done!\tGot      1,000,000 took   0.0309 sec, thr: 82.902 Gbps, theoretical: 82.902 Gbps, difference: -0.0009 Gbps\n",
      "Sent      1,000,000 size:  384-Byte done!\tGot      1,000,000 took   0.0360 sec, thr: 85.334 Gbps, theoretical: 85.333 Gbps, difference: -0.0009 Gbps\n",
      "Sent      1,000,000 size:  448-Byte done!\tGot      1,000,000 took   0.0411 sec, thr: 87.160 Gbps, theoretical: 87.160 Gbps, difference: -0.0009 Gbps\n",
      "Sent      1,000,000 size:  512-Byte done!\tGot      1,000,000 took   0.0462 sec, thr: 88.582 Gbps, theoretical: 88.581 Gbps, difference: -0.0009 Gbps\n",
      "Sent      1,000,000 size:  576-Byte done!\tGot      1,000,000 took   0.0514 sec, thr: 89.721 Gbps, theoretical: 89.720 Gbps, difference: -0.0009 Gbps\n",
      "Sent      1,000,000 size:  640-Byte done!\tGot      1,000,000 took   0.0565 sec, thr: 90.652 Gbps, theoretical: 90.652 Gbps, difference: -0.0009 Gbps\n",
      "Sent      1,000,000 size:  704-Byte done!\tGot      1,000,000 took   0.0616 sec, thr: 91.430 Gbps, theoretical: 91.429 Gbps, difference: -0.0009 Gbps\n",
      "Sent      1,000,000 size:  768-Byte done!\tGot      1,000,000 took   0.0667 sec, thr: 92.087 Gbps, theoretical: 92.086 Gbps, difference: -0.0010 Gbps\n",
      "Sent      1,000,000 size:  832-Byte done!\tGot      1,000,000 took   0.0718 sec, thr: 92.651 Gbps, theoretical: 92.650 Gbps, difference: -0.0010 Gbps\n",
      "Sent      1,000,000 size:  896-Byte done!\tGot      1,000,000 took   0.0770 sec, thr: 93.140 Gbps, theoretical: 93.139 Gbps, difference: -0.0010 Gbps\n",
      "Sent      1,000,000 size:  960-Byte done!\tGot      1,000,000 took   0.0821 sec, thr: 93.568 Gbps, theoretical: 93.567 Gbps, difference: -0.0010 Gbps\n",
      "Sent      1,000,000 size: 1024-Byte done!\tGot      1,000,000 took   0.0872 sec, thr: 93.946 Gbps, theoretical: 93.945 Gbps, difference: -0.0010 Gbps\n",
      "Sent      1,000,000 size: 1088-Byte done!\tGot      1,000,000 took   0.0923 sec, thr: 94.282 Gbps, theoretical: 94.281 Gbps, difference: -0.0010 Gbps\n",
      "Sent      1,000,000 size: 1152-Byte done!\tGot      1,000,000 took   0.0974 sec, thr: 94.582 Gbps, theoretical: 94.581 Gbps, difference: -0.0010 Gbps\n",
      "Sent      1,000,000 size: 1216-Byte done!\tGot      1,000,000 took   0.1026 sec, thr: 94.853 Gbps, theoretical: 94.852 Gbps, difference: -0.0010 Gbps\n",
      "Sent      1,000,000 size: 1280-Byte done!\tGot      1,000,000 took   0.1077 sec, thr: 95.098 Gbps, theoretical: 95.097 Gbps, difference: -0.0010 Gbps\n",
      "Sent      1,000,000 size: 1344-Byte done!\tGot      1,000,000 took   0.1128 sec, thr: 95.320 Gbps, theoretical: 95.319 Gbps, difference: -0.0010 Gbps\n",
      "Sent      1,000,000 size: 1408-Byte done!\tGot      1,000,000 took   0.1179 sec, thr: 95.523 Gbps, theoretical: 95.522 Gbps, difference: -0.0010 Gbps\n",
      "Sent      1,000,000 size: 1472-Byte done!\tGot      1,000,000 took   0.1230 sec, thr: 95.710 Gbps, theoretical: 95.709 Gbps, difference: -0.0010 Gbps\n",
      "Sent  1,000,000,000 size:   64-Byte done!\tGot  1,000,000,000 took  10.3999 sec, thr: 49.231 Gbps, theoretical: 49.231 Gbps, difference: -0.0005 Gbps\n",
      "Sent  1,000,000,000 size:  128-Byte done!\tGot  1,000,000,000 took  16.7056 sec, thr: 61.297 Gbps, theoretical: 65.979 Gbps, difference: 4.6826 Gbps\n",
      "Sent  1,000,000,000 size:  192-Byte done!\tGot  1,000,000,000 took  20.6398 sec, thr: 74.419 Gbps, theoretical: 74.419 Gbps, difference: -0.0007 Gbps\n",
      "Sent  1,000,000,000 size:  256-Byte done!\tGot  1,000,000,000 took  25.7597 sec, thr: 79.504 Gbps, theoretical: 79.503 Gbps, difference: -0.0008 Gbps\n",
      "Sent  1,000,000,000 size:  320-Byte done!\tGot  1,000,000,000 took  30.8797 sec, thr: 82.902 Gbps, theoretical: 82.902 Gbps, difference: -0.0008 Gbps\n",
      "Sent  1,000,000,000 size:  384-Byte done!\tGot  1,000,000,000 took  35.9996 sec, thr: 85.334 Gbps, theoretical: 85.333 Gbps, difference: -0.0008 Gbps\n",
      "Sent  1,000,000,000 size:  448-Byte done!\tGot  1,000,000,000 took  41.1196 sec, thr: 87.160 Gbps, theoretical: 87.160 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size:  512-Byte done!\tGot  1,000,000,000 took  46.2395 sec, thr: 88.582 Gbps, theoretical: 88.581 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size:  576-Byte done!\tGot  1,000,000,000 took  51.3595 sec, thr: 89.721 Gbps, theoretical: 89.720 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size:  640-Byte done!\tGot  1,000,000,000 took  56.4794 sec, thr: 90.652 Gbps, theoretical: 90.652 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size:  704-Byte done!\tGot  1,000,000,000 took  61.5994 sec, thr: 91.429 Gbps, theoretical: 91.429 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size:  768-Byte done!\tGot  1,000,000,000 took  66.7193 sec, thr: 92.087 Gbps, theoretical: 92.086 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size:  832-Byte done!\tGot  1,000,000,000 took  71.8393 sec, thr: 92.651 Gbps, theoretical: 92.650 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size:  896-Byte done!\tGot  1,000,000,000 took  76.9592 sec, thr: 93.140 Gbps, theoretical: 93.139 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size:  960-Byte done!\tGot  1,000,000,000 took  82.0792 sec, thr: 93.568 Gbps, theoretical: 93.567 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size: 1024-Byte done!\tGot  1,000,000,000 took  87.1991 sec, thr: 93.946 Gbps, theoretical: 93.945 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size: 1088-Byte done!\tGot  1,000,000,000 took  92.3191 sec, thr: 94.282 Gbps, theoretical: 94.281 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size: 1152-Byte done!\tGot  1,000,000,000 took  97.4390 sec, thr: 94.582 Gbps, theoretical: 94.581 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size: 1216-Byte done!\tGot  1,000,000,000 took 102.5590 sec, thr: 94.853 Gbps, theoretical: 94.852 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size: 1280-Byte done!\tGot  1,000,000,000 took 107.6789 sec, thr: 95.098 Gbps, theoretical: 95.097 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size: 1344-Byte done!\tGot  1,000,000,000 took 112.7989 sec, thr: 95.320 Gbps, theoretical: 95.319 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size: 1408-Byte done!\tGot  1,000,000,000 took 117.9188 sec, thr: 95.523 Gbps, theoretical: 95.522 Gbps, difference: -0.0009 Gbps\n",
      "Sent  1,000,000,000 size: 1472-Byte done!\tGot  1,000,000,000 took 123.0388 sec, thr: 95.710 Gbps, theoretical: 95.709 Gbps, difference: -0.0009 Gbps\n"
     ]
    }
   ],
   "source": [
    "import time\n",
    "# overhead is UDP (8), IP (20), Ethernet(14) and FCS (4), IFG (12), preamble (7), start frame delimiter (1)\n",
    "overhead = 8 + 20 + 14 + 4 + 12 + 7 + 1\n",
    "freq = int(ol_w1.clock_dict['clock0']['frequency'])\n",
    "ol_w0_tg = ol_w0.traffic_generator_1_3\n",
    "experiment_dict = {}\n",
    "local_dict = {}\n",
    "ol_w1_tg.freq = freq\n",
    "ol_w0_tg.freq = freq\n",
    "for pkt in [1_000_000, 1_000_000_000]:\n",
    "    ol_w0_tg.reset_stats()\n",
    "    ol_w1_tg.reset_stats()\n",
    "    local_dict = {}\n",
    "    for i in range(23):\n",
    "        beats = i + 1\n",
    "        ol_w0_tg.start(TgMode.PRODUCER, 7, pkt, beats, 0)\n",
    "        while int(ol_w0_tg.register_map.out_traffic_packets) != pkt:\n",
    "            time.sleep(0.8)\n",
    "        # Get results from local and remote worker\n",
    "        rx_tot_pkt, rx_thr, rx_time = ol_w1_tg.compute_app_throughput('rx')\n",
    "        tx_tot_pkt, tx_thr, tx_time = ol_w0_tg.compute_app_throughput('tx')\n",
    "        #Create dict entry for this particular experiment\n",
    "        entry_dict = {'size': (beats * 64), 'rx_pkts' : rx_tot_pkt, 'tx_thr': tx_thr, 'rx_thr': rx_thr}\n",
    "        local_dict[beats] = entry_dict\n",
    "        # Reset probes to prepare for next computation\n",
    "        ol_w0_tg.reset_stats()\n",
    "        ol_w1_tg.reset_stats() \n",
    "        theoretical = (beats * 64 * 100)/((beats*64) + overhead) \n",
    "        print(\"Sent {:14,} size: {:4}-Byte done!\\tGot {:14,} took {:8.4f} sec, thr: {:.3f} Gbps, theoretical: {:.3f}, difference: {:.4f}\"\\\n",
    "              .format(pkt,beats*64, rx_tot_pkt, rx_time, rx_thr, theoretical, theoretical-rx_thr))\n",
    "        time.sleep(0.5)\n",
    "    experiment_dict[pkt] = local_dict"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plot the results\n",
    "Finally we can plot the results using matplotlib"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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XQAw1vT4AGCdU1V/VVUPq8HtJt+ewRwBK/5vOAAAjF/wUVWnVNXNVXXlkHXTq+r8qvp597hpt76q6l+q6hdV9eMa3E8dAFblKRncNeKWDIY7DL1rBgAA3GfchrpU1TMymJDs8621x3frPpjk1tba+6tqdgYzeh9bVQdmMEbzwCR7Jvnn1tqe41IYAAAA8Gdj3Hp8tNYuzuDe9EPNymCSunSPhwxZ//k28P0kD6sqE0MBAAAAa2TKBLf38O7WX2mt3bj81nhJtsn9Z/W+vlt344onqKrXJHlNkjzkIQ954o477ji+FQMAAABrtQULFtzSWttiZdsmOvhYlVrJupWOwWmtnZzk5CSZOXNmmz9//njWBQAAAKzlquraVW2b6Lu63LR8CEv3eHO3/vokjxiy37ZJbpjg2gAAAICemejg48tJjuyeH5nk3CHrj+ju7rJXkiXLh8QAAAAAjNa4DXWpqrlJ9k2yeVVdn+Qfk7w/yRer6pVJfp3khd3uX8vgji6/SHJXkr8Zr7oAAACAPx/jFny01l66ik37rWTfluTvxqLde++9N9dff33uueeesTgda5Fp06Zl2223zdSpUye7FAAAANYRa8vkpmPm+uuvz8Ybb5wZM2akamVzprIuaq1l8eLFuf7667PddttNdjkAAACsIyZ6jo9xd88992SzzTYTevRMVWWzzTbTkwcAAIAHpHfBRxKhR095XwEAAHigehl8AAAAACQ9nONjRTNmnzem51v4/ueOaL+zzz47hx56aK6++ursuOOOK92nqvKyl70sp512WpJk6dKl2WqrrbLnnnvmq1/9ar785S/nqquuyuzZs3Pcccdlo402ytFHH52Xv/zlOeigg/KCF7wgr3rVq/KWt7wlj3vc49bodc2ZMydvfetbs8022+T3v/993vzmN+fVr371Az7PjBkzMn/+/Gy++eYj2v9973tf3v72tz/gdgAAAGAk9PgYJ3Pnzs3Tnva0nHnmmavc5yEPeUiuvPLK3H333UmS888/P9tss8192w8++ODMnj172HY+85nPrHHosdyLX/ziXH755bnooovy9re/PTfddNOYnHc473vf+8a9DQAAAP58CT7GwR133JFLLrkkn/3sZ4cNPpLkOc95Ts47b9ArZe7cuXnpS/94F+A5c+bk7//+74c9ft999838+fPvO36XXXbJ4x//+Bx77LH37bPRRhvlHe94R3bbbbfstddeqw00ttxyyzz60Y/Otddem0svvTR77713nvCEJ2TvvffOT3/60yTJsmXLcvTRR2eXXXbJrrvumo9+9KP3O8fdd9+dAw44IJ/+9KeTJF/4whfy5Cc/Obvvvnte+9rXZtmyZZk9e3buvvvu7L777jn88MNz55135rnPfW522223PP7xj89ZZ501bJ0AAACwOoKPcXDOOefkgAMOyA477JDp06fnsssuW+W+L3nJS3LmmWfmnnvuyY9//OPsueeeo2rzhhtuyLHHHpsLL7wwl19+eX74wx/mnHPOSZLceeed2WuvvXLFFVfkGc94xn1hxKpcc801ueaaa7L99ttnxx13zMUXX5wf/ehHOf744+8blnLyySfnV7/6VX70ox/lxz/+cQ4//PD7jr/jjjvyvOc9L4cddlhe/epX5+qrr85ZZ52VSy65JJdffnnWX3/9nH766Xn/+9+fDTbYIJdffnlOP/30fOMb38jWW2+dK664IldeeWUOOOCAUV0LAAAAWK73c3xMhrlz5+ZNb3pTkkGwMXfu3Oyxxx4r3XfXXXfNwoULM3fu3Bx44IGjbvOHP/xh9t1332yxxRZJksMPPzwXX3xxDjnkkDzoQQ/KQQcdlCR54hOfmPPPP3+l5zjrrLPy3e9+Nw9+8IPzqU99KtOnT891112XI488Mj//+c9TVbn33nuTJN/61rdy1FFHZcqUwUdo+vTp951n1qxZOeaYY+4LQy644IIsWLAgT3rSk5IMeoNsueWWf9L+LrvskqOPPjrHHntsDjrooDz96U8f9fUAAACARPAx5hYvXpwLL7wwV155Zaoqy5YtS1Xlgx/84Cpvx3rwwQfn6KOPzkUXXZTFixePqt3W2iq3TZ069b62119//SxdunSl+734xS/OSSeddL9173znO/PMZz4zZ599dhYuXJh99933vvZW9Xqe+tSn5utf/3oOO+ywVFVaaznyyCNzwgknDPsadthhhyxYsCBf+9rX8ra3vS37779/3vWudw17DAAAAAzHUJcxNm/evBxxxBG59tprs3Dhwlx33XXZbrvt8t3vfneVx7ziFa/Iu971ruyyyy6jbnfPPffMd77zndxyyy1ZtmxZ5s6dm3322WfU51tuyZIl9024OmfOnPvW77///vnkJz95X4hy66233rft+OOPz2abbZbXve51SZL99tsv8+bNy80333zfvtdee22SQSizvBfJDTfckA033DAve9nLcvTRRw87RAgAAABGovc9PkZ6+9mxMnfu3D+5E8vzn//8nHHGGascurHtttvmjW984xq1u9VWW+WEE07IM5/5zLTWcuCBB2bWrFlrdM4kOeaYY3LkkUfmQx/6UJ71rGfdt/5Vr3pVfvazn2XXXXfN1KlT8+pXv/p+E7F+5CMfySte8Yocc8wx+eAHP5j3vOc92X///fOHP/whU6dOzcc+9rE88pGPzGte85rsuuuu2WOPPXLEEUfkrW99a9Zbb71MnTo1n/jEJ9a4fgAAAP681XBDJNZ2M2fObMvvaLLc1VdfnZ122mmSKmK8eX8BAABYUVUtaK3NXNk2Q10AAACA3hJ8AAAAAL0l+AAAAAB6S/ABAAAA9JbgAwAAAOgtwQcAAADQW1Mmu4Bxd9xDx/h8S0a029lnn51DDz00V199dXbccceV7rP++utnl112SWst66+/fk466aTsvffeueGGG/KGN7wh8+bNy0UXXZQTTzwxX/3qVzNnzpzMnz8/J510Uj75yU9mww03zBFHHLFGL2fhwoXZaaed8tjHPja///3v84xnPCMf//jHs956DywTe/nLX56DDjooL3jBC0a0/5w5c7L//vtn6623Hk3ZAAAAMCJ6fIyTuXPn5mlPe1rOPPPMVe6zwQYb5PLLL88VV1yRE044IW9729uSJFtvvXXmzZs37PmPOuqoNQ49lnv0ox+dyy+/PD/+8Y9z1VVX5ZxzzhmT8w5nzpw5ueGGG8a9HQAAAP689b/HxyS44447cskll+Tb3/52Dj744Bx33HGrPeZ3v/tdNt100ySDXhgHHXRQrrzyylXuf9xxx2WjjTbK0UcfncsvvzxHHXVU7rrrrjz60Y/OKaeckk033TT77rtv9txzz3z729/Obbfdls9+9rN5+tOfvspzTpkyJXvvvXd+8Ytf5I477sisWbPy29/+Nvfee2/e8573ZNasWUmSz3/+8znxxBNTVdl1111z2mmn3e8873znO3PdddfllFNOyY9+9KO85S1vyR133JHNN988c+bMySWXXJL58+fn8MMPzwYbbJDvfe97efe7350vf/nLmTJlSvbff/+ceOKJI7jSAAAA/TVj9nmjOm7htMNG1+AIRzisawQf4+Ccc87JAQcckB122CHTp0/PZZddlj322ONP9rv77ruz++6755577smNN96YCy+8cFTtHXHEEfnoRz+affbZJ+9617vy7ne/Ox/5yEeSJEuXLs2ll16ar33ta3n3u9+db33rW6s8z1133ZULLrggxx9/fKZNm5azzz47m2yySW655ZbstddeOfjgg3PVVVflve99by655JJsvvnmufXWW+93jmOOOSZLlizJ5z73uSxdujSvf/3rc+6552aLLbbIWWedlXe84x055ZRTctJJJ+XEE0/MzJkzc+utt+bss8/OT37yk1RVbrvttlFdBwAAAFiR4GMczJ07N29605uSJC95yUsyd+7clQYfy4e6JMn3vve9HHHEEcP28liZJUuW5Lbbbss+++yTJDnyyCPzwhe+8L7thx56aJLkiU98YhYuXLjSc/zyl7/M7rvvnqrKrFmz8pznPCf33ntv3v72t+fiiy/Oeuutl9/85je56aabcuGFF+YFL3hBNt988yTJ9OnT7zvPP/3TP2XPPffMySefnCT56U9/miuvvDLPfvazkyTLli3LVltt9Sftb7LJJpk2bVpe9apX5bnPfW4OOuigB3QNAAAAHojR9qRI9KZYFwk+xtjixYtz4YUX5sorr0xVZdmyZamqfPCDH0xVrfK4pzzlKbnllluyaNGiMa3nwQ9+cJLBRKpLly5d6T7L5/gY6vTTT8+iRYuyYMGCTJ06NTNmzMg999yT1toqX8eTnvSkLFiwILfeemumT5+e1lp23nnnfO973xu2xilTpuTSSy/NBRdckDPPPDMnnXTSqHu/AAAAk2vCh2ckQgWGJfgYY/PmzcsRRxyRT33qU/et22efffLd73532Pk1fvKTn2TZsmXZbLPNctddd424vYc+9KHZdNNN8+///u95+tOfntNOO+2+3h9rYsmSJdlyyy0zderUfPvb3861116bJNlvv/3y13/913nzm9+czTbb7L6QI0kOOOCA/NVf/VWe+9zn5pvf/GYe+9jHZtGiRfne976XpzzlKbn33nvzs5/9LDvvvHM23njj3H777UkGc6LcddddOfDAA7PXXntl++23X+P6AQCgb/RSgNHpf/AxwV/UuXPnZvbs2fdb9/znPz9nnHHGnwQfy+f4SJLWWk499dSsv/76D7jNU0899b7JTR/1qEflc5/73OhfQOfwww/P8573vMycOTO7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\n",
      "text/plain": [
       "<Figure size 1332x504 with 1 Axes>"
      ]
     },
     "metadata": {
      "needs_background": "light"
     },
     "output_type": "display_data"
    }
   ],
   "source": [
    "%matplotlib inline\n",
    "import matplotlib.pyplot as plt\n",
    "\n",
    "dict_oneM = experiment_dict[1_000_000]\n",
    "dict_oneB = experiment_dict[1_000_000_000]\n",
    "labels = []\n",
    "oneM_thr = []\n",
    "oneB_thr = []\n",
    "\n",
    "for b in dict_oneM:\n",
    "    labels.append(dict_oneM[b]['size'])\n",
    "    oneM_thr.append(dict_oneM[b]['rx_thr'])\n",
    "\n",
    "for b in dict_oneB:\n",
    "    oneB_thr.append(dict_oneB[b]['rx_thr'])\n",
    "\n",
    "x = np.arange(len(labels))  # the label locations\n",
    "width = 0.35  # the width of the bars\n",
    "\n",
    "fig, ax = plt.subplots()\n",
    "rects1 = ax.bar(x - width/2, oneM_thr, width, label='A Million Packets')\n",
    "rects2 = ax.bar(x + width/2, oneB_thr, width, label='A Billion Packets')\n",
    "\n",
    "# Add some text for labels, title and custom x-axis tick labels, etc.\n",
    "ax.set_ylabel('Throughput (Gbit/s)')\n",
    "ax.set_xlabel('Payload Size (Byte)')\n",
    "ax.set_title('Throughput for different packet size at application level')\n",
    "ax.set_xticks(x)\n",
    "ax.set_xticklabels(labels)\n",
    "ax.legend()\n",
    "ax.set_ylim(40,100)\n",
    "fig.set_size_inches(18.5, 7)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Release Alveo cards\n",
    "To release the alveo cards the pynq overlay is freed"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "pynq.Overlay.free(ol_w0)\n",
    "pynq.Overlay.free(ol_w1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "------------------------------------------\n",
    "Copyright (c) 2020-2021, Xilinx, Inc."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}